﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace Util.Web
{
    // V 1.0.1 - 2025-06-27 17:46:16
    // 1. 修改 Discover 改为非静态方法, 增加参数 需要扫描的网段集合
    // 2. 增加 3 个事件, 用于了解 Discover 执行的状态
    // 3. Discover 函数增加 connectTimeout 参数, 默认 150 毫秒

    /// <summary>
    /// LAN (Local Area Network) 扫描器工具类
    /// </summary>
    public class LANScannerUtils
    {
        /// <summary>
        /// 发现安卓设备事件
        /// </summary>
        public event EventHandler<EventArgs<ScanResult>> PortDiscoverd;

        /// <summary>
        /// 函数 Discover 的执行状态事件
        /// </summary>
        public event EventHandler<EventArgs<string>> DiscoverStatus;

        /// <summary>
        /// 函数 Discover 的执行明细状态事件
        /// </summary>
        public event EventHandler<EventArgs<ScanResult>> DiscoverDetailStatus;

        /// <summary>
        /// 扫描局域网内指定端口的设备。
        /// </summary>
        /// <param name="port">要扫描的端口号。</param>
        /// <param name="connectTimeout">连接超时 (可空) 默认 200 毫秒</param>
        /// <param name="intersectLocalIP">需要扫描的网段集合</param>
        /// <returns>返回一个包含扫描结果的列表。</returns>
        public List<ScanResult> Discover(int port, int connectTimeout = 150, IEnumerable<IPAddress> intersectLocalIP = null)
        {
            DiscoverStatus?.Invoke(this, new EventArgs<string>("正在获取本机 IP 和子网范围"));

            // 获取本机 IP 和子网范围
            IEnumerable<IPAddress> localIPs = GetLocal_IPv4_List();
            if (localIPs.Count() == 0)
            {
                throw new Exception("无法获取本地 IP 地址！");
            }

            if (intersectLocalIP != null) // 传入了需要扫描的网段集合, 计算交集
            {
                localIPs = localIPs.Intersect(intersectLocalIP); // 计算交集
                DiscoverStatus?.Invoke(this, new EventArgs<string>($"传递了 intersectLocalIP 参数, 故只扫描以下网段。 {localIPs.Select(i => $"{LANScannerUtils.GetSubnet(i)}.*").CombineString(", ")}"));
            }

            List<ScanResult> r = new List<ScanResult>();

            foreach (var ip in localIPs)
            {
                string subnet = GetSubnet(ip);
                DiscoverStatus?.Invoke(this, new EventArgs<string>($"正在扫描子网: {subnet}.*"));

                // 异步扫描所有可能的 IP 地址
                List<Task<ScanResult>> tasks = new List<Task<ScanResult>>();
                for (int i = 1; i <= 254; i++)
                {
                    string targetIP = $"{subnet}.{i}";
                    Task<ScanResult> toAdd = Task.Run<ScanResult>(() =>
                    {
                        Task<ScanResult> checkPortResult = CheckPortAsync(targetIP, port, connectTimeout);
                        DiscoverDetailStatus?.Invoke(this, new EventArgs<ScanResult>(checkPortResult.Result));
                        if (checkPortResult.Result.IsSuccess)
                        {
                            PortDiscoverd?.Invoke(this, new EventArgs<ScanResult>(checkPortResult.Result));
                        }
                        return checkPortResult.Result;
                    });                    
                    tasks.Add(toAdd);
                }

                // 等待所有任务完成
                var taskResults = Task.WhenAll(tasks);

                // 筛选出成功的结果添加到返回结果 r 中
                var toAddResult = taskResults.Result.Where(i => i.IsSuccess == true).ToList();

                if (toAddResult != null && toAddResult.Count > 0)
                {
                    r.AddRange(toAddResult);
                }
            }

            DiscoverStatus?.Invoke(this, new EventArgs<string>("结束扫描局域网内的安卓设备。"));
            return r;
        }

        /// <summary>
        /// 获取本机所有的 IPv4 地址。
        /// </summary>
        /// <returns>返回本机的 IPv4 地址列表。</returns>
        public static IEnumerable<IPAddress> GetLocal_IPv4_List()
        {
            return Dns.GetHostEntry(Dns.GetHostName())
                      .AddressList
                      .Where(ip => ip.AddressFamily == AddressFamily.InterNetwork);
        }

        /// <summary>
        /// 获取指定 IP 地址的子网前缀。
        /// </summary>
        /// <param name="ip">IP 地址。</param>
        /// <returns>返回子网前缀字符串。</returns>
        public static string GetSubnet(IPAddress ip)
        {
            byte[] bytes = ip.GetAddressBytes();
            // $"{bytes[0]}.{bytes[1]}.{bytes[2]}.{bytes[3]}" // 完整的IP地址
            return $"{bytes[0]}.{bytes[1]}.{bytes[2]}"; // 这里只截取前3位, 最后一位在 for 循环中拼接
        }

        /// <summary>
        /// 检查指定 IP 和端口是否开放。
        /// </summary>
        /// <param name="ip">目标 IP 地址。</param>
        /// <param name="port">目标端口号。</param>
        /// <param name="millisecondsTimeout">默认等待连接时间 1000 毫秒</param>
        /// <returns>返回一个包含检查结果的任务。</returns>
        Task<ScanResult> CheckPortAsync(string ip, int port, int millisecondsTimeout = 1000)
        {
            return Task.Factory.StartNew(() =>
            {
                using (TcpClient client = new TcpClient())
                {
                    try
                    {
                        // 使用 BeginConnect 和 EndConnect 实现异步连接
                        var asyncResult = client.BeginConnect(ip, port, null, null);

                        // 设置超时时间
                        bool isConnected = asyncResult.AsyncWaitHandle.WaitOne(millisecondsTimeout);

                        if (isConnected && client.Connected)
                        {
                            client.EndConnect(asyncResult);
                            return new ScanResult(ip, port, true); // 端口开放
                        }
                        else
                        {
                            return new ScanResult(ip, port, false); // 端口关闭
                        }
                    }
                    catch
                    {
                        return new ScanResult(ip, port, false); // 端口关闭
                    }
                }
            });
        }

        /// <summary>
        /// 表示扫描结果的类。
        /// </summary>
        public class ScanResult
        {
            /// <summary>
            /// 初始化 <see cref="ScanResult"/> 类的新实例。
            /// </summary>
            /// <param name="ip">设备的 IP 地址。</param>
            /// <param name="port">设备的端口号。</param>
            /// <param name="isSuccess">是否成功连接。</param>
            public ScanResult(string ip, int port, bool isSuccess)
            {
                this.IP = ip;
                this.Port = port;
                this.IsSuccess = isSuccess;
            }

            /// <summary>
            /// 获取设备的 IP 地址。
            /// </summary>
            public string IP { get; private set; }

            /// <summary>
            /// 获取设备的端口号。
            /// </summary>
            public int Port { get; private set; }

            /// <summary>
            /// 获取是否成功连接的状态。
            /// </summary>
            public bool IsSuccess { get; private set; }
        }
    }
}
